package com.sh.diytwo;

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @Auther: sh
 * @Version 1.0
 * @Description: 有界阻塞队列
 */
public class MyBlockQueueTwo<T> {

    // 创建一个队列：Deque有很多实现类，大多数情况下使用ArrayDeque
    private Deque<T> deque = new ArrayDeque<>();
    // 队列容量
    private int size;

    /*
    为什么定义锁？
    生产者往阻塞队列去添加任务，消费者从队头拿任务
    线程（生产者/消费者）可能有很多，任务（任务队列）只有一个，多线程去共享我们的一个资源，就会带来线程安全的问题
    */
    private ReentrantLock lock = new ReentrantLock();
    /*
    有了锁之后，定义两个条件变量，干嘛？
    当消费者去队列拿任务的时候，如果队列是空的，就让这个线程去休息室等待
    当生产者往队列添加任务的时候，如果队列是满的，就让这个线程去休息室等待
    */
    //   消费者
    Condition emptyCondition = lock.newCondition();
    //   生产者
    Condition fullCondition = lock.newCondition();

    public MyBlockQueueTwo(int size) {
        this.size = size;
    }

    /**
     * 向任务队列中插入一个任务，如果队列已满，则阻塞等待直到有空位。
     *
     * @param task 要插入的任务
     */
    public void put(T task) {
        // 获取锁，确保线程安全
        lock.lock();
        try {
            // 如果队列已满，则阻塞等待，直到有空位
            while (deque.size() == size) {
                System.out.println("==========队列满了，生产者等待============");
                // 阻塞当前线程，并释放锁，直到被唤醒
                fullCondition.await();
            }
            // 插入任务到队列末尾
            deque.addLast(task);
            System.out.println("生产者添加任务：" + task);
            // 通知消费者队列不为空，可以消费任务了
            emptyCondition.signal();
        } catch (InterruptedException e) {
            // 如果在等待过程中被中断，则抛出运行时异常
            throw new RuntimeException(e);
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

    /**
     * 尝试在指定的超时时间内向任务队列中插入任务，如果超时仍然无法插入，则返回 false。
     *
     * @param task     要插入的任务
     * @param timeout  最长等待时间
     * @param timeUnit 时间单位（例如 TimeUnit.SECONDS）
     * @return 插入成功返回 true，超时失败返回 false
     */
    public boolean offer(T task, long timeout, TimeUnit timeUnit) {
        // 获取锁，确保线程安全
        lock.lock();
        try {
            // 将等待时间转换为纳秒
            long nanos = timeUnit.toNanos(timeout);
            // 如果队列已满，进入等待逻辑
            while (deque.size() == size) {
                if (nanos <= 0) {
                    // 超时返回 false，不再等待
                    System.out.println("队列满了，生产者等待" + timeUnit.toSeconds(timeout) + "秒后超时.....===========");
                    return false;
                }
                // 提示任务正在等待加入队列
                System.out.println(task + "等待加入任务队列。。。。");
                // 阻塞当前线程指定时间，并更新剩余等待时间
                nanos = fullCondition.awaitNanos(nanos);
            }
            // 队列未满，添加任务
            deque.addLast(task);
            System.out.println(task + "加入任务队列成功");
            // 通知消费者有任务可消费
            emptyCondition.signal();
            return true;
        } catch (InterruptedException e) {
            // 如果在等待过程中被中断，则抛出运行时异常
            throw new RuntimeException(e);
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

    /**
     * 从任务队列中取出一个任务，在指定超时时间内等待任务出现。
     * 如果在规定时间内队列仍为空，则返回 null。
     *
     * @param timeout  最长等待时间
     * @param timeUnit 时间单位（例如 TimeUnit.SECONDS）
     * @return 返回取出的任务，若超时则返回 null
     */
    public T poll(long timeout, TimeUnit timeUnit) {
        // 加锁，确保线程安全
        lock.lock();
        try {
            // 将等待时间转换为纳秒
            long nanos = timeUnit.toNanos(timeout);
            // 如果队列为空，消费者进入等待状态
            while (deque.isEmpty()) {
                if (nanos <= 0) {
                    // 等待超时仍未获取到任务，返回 null
                    System.out.println("======队列空了，消费者等待了" + timeUnit.toSeconds(timeout) + "秒后超时======");
                    return null;
                }
                // 提示消费者正在等待任务加入
                System.out.println("队列空了，消费者等待。。。");
                // 阻塞当前线程，直到有任务或超时，返回剩余时间
                nanos = emptyCondition.awaitNanos(nanos);
            }
            // 队列不为空，取出并返回任务
            T t = deque.removeFirst();
            // 通知生产者队列不满，可以继续添加任务了
            fullCondition.signal();
            System.out.println("消费者消费任务：" + t);
            return t;
        } catch (InterruptedException e) {
            // 等待过程中被中断，抛出运行时异常
            throw new RuntimeException(e);
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

    /**
     * 尝试将任务添加到队列中，如果队列已满，则触发自定义的拒绝策略。
     *
     * @param myRejectPolicyTwo 自定义拒绝策略的实现
     * @param task              要插入的任务
     */
    public void tryPut(MyRejectPolicyTwo<T> myRejectPolicyTwo, T task){
        // 加锁，确保线程安全
        lock.lock();
        try {
            if (deque.size() == size) {
                // 队列已满，调用用户自定义的拒绝策略处理该任务
                myRejectPolicyTwo.reject(this, task);
            } else {
                // 队列未满，插入任务
                deque.addLast(task);
                System.out.println("生产者添加任务：" + task);
                // 通知消费者有任务可消费
                emptyCondition.signal();
            }
        } catch (Exception e) {
            // 出现任何异常时抛出运行时异常
            throw new RuntimeException(e);
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

}

